package analysis;


/**
 * @author Mathieu Rousseau
 * @email mathieu.rousseau3@mail.mcgill.ca
 */


import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;

import utilities.PDButility;


/** ------------------------------------------------------------------------------------------------------------------
 * start of BurnInAnalyzer class
 * ------------------------------------------------------------------------------------------------------------------  */
public class BurnInAnalyzer {

	
	/*  global variables  */
	private final String inputPath = "BurnInAnalyzer/Input/";
	private String outputPath = "BurnInAnalyzer/Output/";
	private int windowSize = 1;
	private int numberOfStructures = 0;
	private PDButility pdb = new PDButility();
	private int numberOfPoints = 0;
	private String experimentNameOne = "default";
	private String experimentNameTwo = "default";
	private ArrayList<double[][]> structuresRunOne;
	private ArrayList<double[][]> structuresRunTwo;
	private ArrayList<double[][]> intraStructureDistancesRunOne;
	private ArrayList<double[][]> intraStructureDistancesRunTwo;
	private double[][] interStructureDistancesRunOne;
	private double[][] interStructureDistancesRunTwo;
	private double[][] interStructureDistancesCrossRuns;
	private double[] averageInterStructureDistancesRunOne;
	private double[] averageInterStructureDistancesRunTwo;
	private double[] averageInterStructureDistancesCrossRuns;
	/*  end of global variables  */
	
	
	/*  start of getStructuresFromFile method  */
	private void getStructuresFromFile(String experimentName, ArrayList<double[][]> structures){
		//  initialize the list that will hold all of the structures
		for(int i=0; i<numberOfStructures; i++){
			double structureInitializer[][] = new double[numberOfPoints][numberOfPoints];
			structures.add(structureInitializer);
		}

		
		//  define the filename of each of the structures to be read from file
		String structureFilename;
		//  use the convention that the structure file names start at index 1
		for(int i=1; i<=numberOfStructures; i++){
			if(i <= 9){
				structureFilename = "structure_000" + i + "_" + experimentName + ".pdb";
			}
			else if(i <= 99){
				structureFilename = "structure_00" + i + "_" + experimentName + ".pdb";
			}
			else if(i <= 999){
				structureFilename = "structure_0" + i + "_" + experimentName + ".pdb";
			}
			else{
				structureFilename = "structure_" + i + "_" + experimentName + ".pdb";
			}
//			System.out.println("Tyring to read structure file: " + inputPath + structureFilename);
			
			//  get the structure from file and store it in the list of structures
			//  remember that the indices in the array list start at zero (0) and not one (1) like the filenames
			structures.set(i-1, pdb.getStructure(inputPath + structureFilename));
		}
	}/*  end of getStructuresFromFile method  */
	
	
	/*  start of calculateIntraStructureDistances method  */
	private void calculateIntraStructureDistances(ArrayList<double[][]> structures, ArrayList<double[][]> intraStructureDistances){
		for(int structureIndex=0; structureIndex<numberOfStructures; structureIndex++){
			double[][] currentStructureIntraDistances = new double[numberOfPoints][numberOfPoints];
//			System.out.println("\tcalculating the intra distances for structure " + (structureIndex+1));
			for(int i=0; i<numberOfPoints-1; i++){
				for(int j=i+1; j<numberOfPoints; j++){
					currentStructureIntraDistances[i][j] = calculateDistanceBetweenPoints(structures.get(structureIndex), i, j);
				}
			}		
			intraStructureDistances.add(currentStructureIntraDistances);			
		}
	}/*  end of calculateIntraStructureDistances method  */

	
	/*  start of calculateInterStructureDistances method  */
	private void calculateInterStructureDistances(ArrayList<double[][]> intraStructureDistancesFirstList, ArrayList<double [][]> intraStructureDistancesSecondList, double[][] interStructureDistances){
		for(int firstStructureIndex=0; firstStructureIndex<numberOfStructures-1; firstStructureIndex++){
			for(int secondStructureIndex=firstStructureIndex+1; secondStructureIndex<numberOfStructures; secondStructureIndex++){
				interStructureDistances[firstStructureIndex][secondStructureIndex] = calculateDistanceBetweenStructures(intraStructureDistancesFirstList.get(firstStructureIndex), intraStructureDistancesSecondList.get(secondStructureIndex));
			}
		}
	}/*  end of calculateInterStructureDistances method  */
	
	
	/*  start of calculateAverageInterStructureDistance method  */
	private void calculateAverageInterStructureDistance(double[][] interStructureDistances, double[] averageInterStructureDistances){
		//  calculate the number of pairs = windowSize! / (2! * (windowSize-2)! )
		double numberOfPairs = windowSize * (windowSize-1) / 2.0;
//		System.out.println("The number of pairs = " + numberOfPairs);
		
		
		//  calculate the average inter structure difference across all pairs of structures within the window
		for(int i=0; i<numberOfStructures-windowSize+1; i++){
			//  go through all the possible pairs of structures in the windows starting at index i
			for(int index1=i; index1<i+windowSize-1; index1++){
				for(int index2=index1+1; index2<i+windowSize; index2++){
					averageInterStructureDistances[i] += interStructureDistances[index1][index2];
				}
			}
			averageInterStructureDistances[i] /= numberOfPairs;
		}		
	}/*  end of calculateAverageInterStructureDistance method  */
	
	
	/*  start of calculateDistanceBetweenPoints method  */
	private double calculateDistanceBetweenPoints(double[][] structure, int coord1, int coord2){
		double term1 = structure[coord2][0] - structure[coord1][0];
		term1 *= term1;
		double term2 = structure[coord2][1] - structure[coord1][1];
		term2 *= term2;
		double term3 = structure[coord2][2] - structure[coord1][2];
		term3 *= term3;
		return Math.sqrt(term1 + term2 + term3);
	}/*  end of calculateDistanceBetweenPoints method  */
	
	
	/*  start of calculateDistanceBetweenStructures method  */
	private double calculateDistanceBetweenStructures(double[][] intraFirstStructureDistances, double[][] intraSecondStructureDistances){
		double distance = 0;
		for(int i=0; i<numberOfPoints-1; i++){
			for(int j=i+1; j<numberOfPoints; j++){
				double term = intraSecondStructureDistances[i][j] - intraFirstStructureDistances[i][j];
				term *= term;
				distance += term;
			}
		}
		return distance;
	}/*  end of calculateDistanceBetweenStructures method  */
	
	
	/*  start of outputInterStructureDistancesToFile method  */
	private void outputInterStructureDistancesToFile(double[][] array, String outputFilename){
		try{
			BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath + outputFilename));
			
			if(array.length == 0){
				bw.write("Note: the array to output had size zero (0). There is no data to output for this array.");
				bw.flush();
			}
			else{
				bw.write("\t");
				bw.flush();
				for(int i=2; i<=numberOfStructures; i++){
					bw.write(i + "\t");
				}
				bw.newLine();
				bw.flush();
				for(int i=0; i<numberOfStructures-1; i++){
					bw.write(String.valueOf(i+1));
					bw.flush();
					for(int j=0; j<numberOfStructures; j++){
						if(j<=i){
							bw.write("\t");
							bw.flush();
						}
						else if(j<numberOfStructures-1){
							bw.write(array[i][j] + "\t");
							bw.flush();
						}
						else{
							bw.write(String.valueOf(array[i][j]));
							bw.flush();
						}						
					}
					bw.newLine();
					bw.flush();
				}
			}
			bw.flush();
			bw.close();
			System.out.println("\tOutput file: " + outputPath + outputFilename);			
		}catch (IOException e) {
			e.printStackTrace();
		}//end of try-catch block
	}/*  end of outputInterStructureDistancesToFile method  */
	
	
	
	/*  start of outputAverageInterStructureDistancesToFile method  */
	private void outputAverageInterStructureDistancesToFile(double[] arrayRunOne, double[] arrayRunTwo, double[] arrayCrossRuns, String outputFilename){
		try{
			BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath + outputFilename));
			
			if(arrayCrossRuns.length == 0){
				bw.write("Note: the array to output had size zero (0). There is no data to output for this array.");
				bw.flush();
			}
			else{
				bw.write("\tRun One\tRun Two\tCross Runs");
				bw.newLine();
				bw.flush();
				for(int i=0; i<numberOfStructures-windowSize+1; i++){
					bw.write("[" + (i+1) + "-" + (i+windowSize) + "]\t");
					bw.flush();
					bw.write(arrayRunOne[i] + "\t" + arrayRunTwo[i] + "\t" + arrayCrossRuns[i]);
					bw.newLine();
					bw.flush();
				}
			}
			bw.flush();
			bw.close();
			System.out.println("\tOutput file: " + outputPath + outputFilename);			
		}catch (IOException e) {
			e.printStackTrace();
		}//end of try-catch block
	}/*  end of outputAverageInterStructureDistancesToFile method  */
	
	
	/*  start of run method  */
	public void run(String[] args) {
		
		//  currently no option to run from the command line (only takes hardcoded parameters)
		
		
		
		
		
		/** ------------------------------------------------------------------------------------------------------------------
		 * 
		 * 
		 * 								HARDCODING OF PARAMETERS
		 * 
		 * 
		 * ------------------------------------------------------------------------------------------------------------------  */
		//  set the number of structures to analyze
		numberOfStructures = 500;
//		System.out.println("\tNumber of structures = " + numberOfStructures);
		//  set the names of the experiments
		//  this is the common suffix to all structure filenames from each of the runs (i.e. filenames = structure_####_experimentName.pdb)
//		experimentNameOne = "MCMC5C_DMSO_1";
//		experimentNameTwo = "MCMC5C_DMSO_2";
		experimentNameOne = "MCMC5C_PMA_1";
		experimentNameTwo = "MCMC5C_PMA_2";
//		System.out.println("\tExperiment name [run One] = " + experimentNameRunOne);
//		System.out.println("\tExperiment name [run Two] = " + experimentNameRunTwo);
		//  set the window size
		windowSize = 10;
		/** ------------------------------------------------------------------------------------------------------------------
		 * 
		 * 
		 * 								END OF HARDCODING OF PARAMETERS
		 * 
		 * 
		 * ------------------------------------------------------------------------------------------------------------------  */
		
		
		
		
		//  display the parameters of the run
		System.out.println("Parameters:");
		if(numberOfStructures < 2){
			System.out.println("Error: cannot compare less than two (2) structures per run.\nSystem.exit(0)");
			System.exit(0);
		}
		else{
			System.out.println("\tUsing a pool of " + numberOfStructures + " structures");
		}
		System.out.println("\twindow size = " + windowSize);
		
		
		//  output the parameters to file
		try{
			String parameterFilename = "parameters.txt";
			BufferedWriter bw = new BufferedWriter(new FileWriter(outputPath + parameterFilename));
			
			bw.write("experimentNameOne=\t" + experimentNameOne);
			bw.newLine();
			bw.flush();
			bw.write("experimentNameTwo=\t" + experimentNameTwo);
			bw.newLine();
			bw.flush();
			bw.write("numberOfStructures=\t" + numberOfStructures);
			bw.newLine();
			bw.flush();
			bw.write("windowSize=\t" + windowSize);
			bw.newLine();
			bw.flush();
			bw.write("command line parameters:");
			for(String arg: args){
				bw.write("\t" + arg);
			}
			bw.newLine();
			bw.flush();
			
			bw.close();
			System.out.println("\tOutput file: " + outputPath + parameterFilename);
		}catch (IOException e) {
			e.printStackTrace();
		}//end of try-catch block
		

		//  calculate how long running BurnInAnalyzer takes (time point before run)
		long timeBefore = System.currentTimeMillis();

		
		System.out.println("\nRunning...\n");
		
		
		//  make sure that the window size is smaller than the number of structures
		if(windowSize >= numberOfStructures){
			System.out.println("Error: the window size (" + windowSize + ") is NOT smaller than the number of structures (" + numberOfStructures + ").");
			System.out.println("System.exit(0)");
			System.exit(0);
		}
		
		
		//  get the number of points in each structure
		numberOfPoints = pdb.getNumberOfPoints(inputPath + "structure_0001_" + experimentNameOne + ".pdb");
//		System.out.println("\tNumber of points = " + numberOfPoints);
		
		
		//  load all of the structures from file
		structuresRunOne = new ArrayList<double[][]>(numberOfStructures);
		getStructuresFromFile(experimentNameOne, structuresRunOne);
//		System.out.println("\tstructuresRunOne.size() = " + structuresRunOne.size());
		structuresRunTwo = new ArrayList<double[][]>(numberOfStructures);
		getStructuresFromFile(experimentNameTwo, structuresRunTwo);
//		System.out.println("\tstructuresRunTwo.size() = " + structuresRunTwo.size());
		
		
		//  calculate the intra structure distances for all of the structures
		intraStructureDistancesRunOne = new ArrayList<double[][]>(numberOfStructures);
		intraStructureDistancesRunTwo = new ArrayList<double[][]>(numberOfStructures);
		calculateIntraStructureDistances(structuresRunOne, intraStructureDistancesRunOne);
		calculateIntraStructureDistances(structuresRunTwo, intraStructureDistancesRunTwo);
//		System.out.println("\tintraStructureDistancesRunOne.size() = " + intraStructureDistancesRunOne.size());
//		System.out.println("\tintraStructureDistancesRunTwo.size() = " + intraStructureDistancesRunTwo.size());
		
		
		//  no longer need the structures so free the memory
		structuresRunOne = null;
		structuresRunTwo = null;
		
		
		//  calculate the inter structure distances for the first run, the second run, and across the two runs
		interStructureDistancesRunOne = new double[numberOfStructures][numberOfStructures];
		interStructureDistancesRunTwo = new double[numberOfStructures][numberOfStructures];
		interStructureDistancesCrossRuns = new double[numberOfStructures][numberOfStructures];
		calculateInterStructureDistances(intraStructureDistancesRunOne, intraStructureDistancesRunOne, interStructureDistancesRunOne);
		calculateInterStructureDistances(intraStructureDistancesRunTwo, intraStructureDistancesRunTwo, interStructureDistancesRunTwo);
		calculateInterStructureDistances(intraStructureDistancesRunOne, intraStructureDistancesRunTwo, interStructureDistancesCrossRuns);
		
		
		//  no longer need the intra structure distances so free the memory
		intraStructureDistancesRunOne = null;
		intraStructureDistancesRunTwo = null;
		
		
		//  calculate the average inter structure distance within each window
		averageInterStructureDistancesRunOne = new double[numberOfStructures];
		averageInterStructureDistancesRunTwo = new double[numberOfStructures];
		averageInterStructureDistancesCrossRuns = new double[numberOfStructures];
		calculateAverageInterStructureDistance(interStructureDistancesRunOne, averageInterStructureDistancesRunOne);
		calculateAverageInterStructureDistance(interStructureDistancesRunTwo, averageInterStructureDistancesRunTwo);
		calculateAverageInterStructureDistance(interStructureDistancesCrossRuns, averageInterStructureDistancesCrossRuns);
		
		
		//  output the inter structure distances to file
		outputInterStructureDistancesToFile(interStructureDistancesRunOne, "interStructureDistancesRunOne.txt");
		outputInterStructureDistancesToFile(interStructureDistancesRunTwo, "interStructureDistancesRunTwo.txt");
		outputInterStructureDistancesToFile(interStructureDistancesCrossRuns, "interStructureDistancesCrossRuns.txt");
		
		
		//  output the average inter structure distances to file
		outputAverageInterStructureDistancesToFile(averageInterStructureDistancesRunOne, averageInterStructureDistancesRunTwo, averageInterStructureDistancesCrossRuns, "averageInterStructureDistances.txt");
		
		
		//  output the time taken by BurnInAnalyzer
		long timeTaken = System.currentTimeMillis() - timeBefore;
		timeTaken = timeTaken / (long)(1000.0);			//	convert from milliseconds to seconds		
		if(timeTaken > 3600){		//	(1hr = 3600 sec)
			int numberOfHours = (int)timeTaken / (int)3600;
			long leftOver = timeTaken % 3600;
			int numberOfMinutes = (int)leftOver / (int)60;
			leftOver = leftOver % 60;
			int numberOfSeconds = (int)leftOver;
			System.out.println("Amount of time taken = " + numberOfHours + "hr" + numberOfMinutes + "min" + numberOfSeconds + "sec");
		}
		else if(timeTaken > 60){		//	(1min = 60 sec)
			int numberOfMinutes = (int)timeTaken / (int)60;
			long leftOver = timeTaken % 60;
			int numberOfSeconds = (int)leftOver;
			System.out.println("Amount of time taken = " + numberOfMinutes + "min" + numberOfSeconds + "sec");
		}
		else{
			int numberOfSeconds = (int)timeTaken;
			System.out.println("Amount of time taken = " + numberOfSeconds + "sec");
		}
		
		
		System.out.print("\nDone!");
	}/*  end of run method  */
}/*  end of BurnInAnalyzer class  */